#include "_tools_oracle.h"

struct st_arg
{
    char connstr[101];     // 数据库的连接参数
    char charset[51];      // 数据库的字符集
    char inifilename[301]; // 数据入库的参数配置文件
    char xmlpath[301];     // 待入库xml文件存放的目录
    char xmlpathbak[301];  // xml文件入库后的备份目录
    char xmlpatherr[301];  // 入库失败的xml文件存放的目录
    int timetvl;           // 本程序运行的时间间隔，本程序常驻内存
    int timeout;           // 本程序运行时的超时时间
    char pname[51];        // 进程名
} starg;

struct st_xmltotable
{
    char filename[101]; // xml文件的匹配规则，用逗号分隔
    char tname[31];     // 待入库的表名
    int uptbz;          // 更新标志，1-更新，2-不更新
    char execsql[301];  // 处理xml文件前，执行的SQL语句
} stxmltotable;

vector<struct st_xmltotable> vxmltotable; // 数据入库的参数的容器
bool loadxmltotable();                    // 把数据入库的参数配置文件starg.inifilename加载到vxmltotable容器中
bool findxmltotable(char *filename);      // 从vxmltotable容器查找xmlfilename的入库参数，存放在stxmltotable结构体中

CLogFile logfile;
connection conn;
CTABCOLS TABCOLS;

void EXIT(int sig);
void _help();
bool _xmltoarg(char *strxmlbuffer);

int totalcount, inscount, uptcount;                                 // xml文件总记录数、插入记录数、更新记录数
bool _xmltodb();                                                    // 业务处理主函数
int _xmltodb(char *fullfilename, char *filename);                   // 调用处理xml文件子函数，返回值：0-成功，其他都是失败
bool xmltobakerr(char *fullfilename, char *srcpath, char *dstpath); // 将xml文件移动到dstpath指定的目录中

char strinsertsql[10241]; // 插入表的SQL语句
char strupdatesql[10241]; // 更新表的SQL语句
void crtsql();            // 拼接生成插入和更新表数据的SQL

#define MAXCOLCOUNT 500 // 每个表字段的最大数量
// #define MAXCOLLEN   100                             // 表字段值的最大长度
// char strcolvalue[MAXCOLCOUNT][MAXCOLLEN+1];         // 存放从xml每一行解析出来的值
char *strcolvalue[MAXCOLCOUNT]; // 存放从xml每一行解析出来的值
sqlstatement stmtins, stmtupt;  // 插入和更新表的sqlstatement对象
void preparesql();              // prepare插入和更新的sql语句，绑定输入变量

bool execsql();                    // 在处理xml文件之前,如果stxmltotable.execsql不为空就执行它
void splitbuffer(char *strBuffer); // 解析xml，存放在已绑定的输入变量中

CPActive PActive; // 进程心跳

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        _help();
        return -1;
    }

    // 处理程序的退出信号
    // CloseIOAndSignal();
    signal(SIGINT, EXIT);
    signal(SIGTERM, EXIT);

    // 打开日志文件
    if (logfile.Open(argv[1], "a+") == false)
    {
        cout << "open logfile failed " << argv[1] << endl;
        return -1;
    }

    // 解析xml，得到程序运行的参数
    if (_xmltoarg(argv[2]) == false)
    {
        return -1;
    }

    PActive.AddPInfo(starg.timeout, starg.pname); // 把进程心跳信息写入共享内存

    // 业务处理主函数
    _xmltodb();

    return 0;
}

void EXIT(int sig)
{
    cout << "sig = " << sig << endl;
    exit(0);
}

void _help()
{
    printf("Using:/project/tools1/bin/xmltodb_oracle logfilename xmlbuffer\n\n");

    printf("Sample:/project/tools1/bin/procctl 10 /project/tools1/bin/xmltodb_oracle /log/idc/xmltodb_oracle_vip1.log \"<connstr>qxidc/1111@snorcl11g_142</connstr><charset>Simplified Chinese_China.AL32UTF8</charset><inifilename>/project/idc/ini/xmltodb.xml</inifilename><xmlpath>/idcdata/xmltodb/vip2</xmlpath><xmlpathbak>/idcdata/xmltodb/vip2bak</xmlpathbak><xmlpatherr>/idcdata/xmltodb/vip2err</xmlpatherr><timetvl>5</timetvl><timeout>50</timeout><pname>xmltodb_oracle_vip2</pname>\"\n\n");

    printf("本程序是数据中心的公共功能模块，用于把xml文件入库到Oracle的表中。\n");
    printf("logfilename   本程序运行的日志文件。\n");
    printf("xmlbuffer     本程序运行的参数，用xml表示，具体如下：\n\n");

    printf("connstr     数据库的连接参数，格式：ip,username,password,dbname,port。\n");
    printf("charset     数据库的字符集，这个参数要与数据源数据库保持一致，否则会出现中文乱码的情况。\n");
    printf("inifilename 数据入库的参数配置文件。\n");
    printf("xmlpath     待入库xml文件存放的目录。\n");
    printf("xmlpathbak  xml文件入库后的备份目录。\n");
    printf("xmlpatherr  入库失败的xml文件存放的目录。\n");
    printf("timetvl     本程序的时间间隔，单位：秒，视业务需求而定，2-30之间。\n");
    printf("timeout     本程序的超时时间，单位：秒，视xml文件大小而定，建议设置30以上。\n");
    printf("pname       进程名，尽可能采用易懂的、与其它进程不同的名称，方便故障排查。\n\n");
}

bool _xmltoarg(char *strxmlbuffer)
{
    memset(&starg, 0, sizeof(struct st_arg));

    GetXMLBuffer(strxmlbuffer, "connstr", starg.connstr, 100);
    if (strlen(starg.connstr) == 0)
    {
        logfile.Write("connstr is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "charset", starg.charset, 50);
    if (strlen(starg.charset) == 0)
    {
        logfile.Write("charset is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "inifilename", starg.inifilename, 300);
    if (strlen(starg.inifilename) == 0)
    {
        logfile.Write("inifilename is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "xmlpath", starg.xmlpath, 300);
    if (strlen(starg.xmlpath) == 0)
    {
        logfile.Write("xmlpath is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "xmlpathbak", starg.xmlpathbak, 300);
    if (strlen(starg.xmlpathbak) == 0)
    {
        logfile.Write("xmlpathbak is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "xmlpatherr", starg.xmlpatherr, 300);
    if (strlen(starg.xmlpatherr) == 0)
    {
        logfile.Write("xmlpatherr is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "timetvl", &starg.timetvl);
    if (starg.timetvl < 2)
        starg.timetvl = 2;
    if (starg.timetvl > 30)
        starg.timetvl = 30;

    GetXMLBuffer(strxmlbuffer, "timeout", &starg.timeout);
    if (starg.timeout == 0)
    {
        logfile.Write("timeout is null.\n");
        return false;
    }

    GetXMLBuffer(strxmlbuffer, "pname", starg.pname, 50);
    if (strlen(starg.pname) == 0)
    {
        logfile.Write("pname is null.\n");
        return false;
    }

    return true;
}

bool _xmltodb()
{
    int count = 50; // 加载入库参数的计数器，初始化为50是为了在第一次进入循环时就加载参数
    CDir Dir;

    while (true)
    {
        if (count++ > 30)
        {
            count = 0; // 重新计数
            // 把数据入库的参数配置文件starg.inifilename加载到容器中
            if (loadxmltotable() == false)
            {
                return false;
            }
        }

        // 打开starg.xmlpath目录，为了保证先生成的文件先入库，打开目录时OpenDir对文件进行排序
        if (Dir.OpenDir(starg.xmlpath, "*.xml", 10000, false, true) == false)
        {
            logfile.Write("Dir.OpenDir(%s) failed.\n", starg.xmlpath);
            return false;
        }

        while (true)
        {
            // 读取目录，得到1个xml文件
            if (Dir.ReadDir() == false)
            {
                break;
            }
            logfile.Write("处理文件%s...", Dir.m_FullFileName);

            // 连接数据库
            if (conn.m_state == 0)
            {
                if (conn.connecttodb(starg.connstr, starg.charset) != 0)
                {
                    logfile.Write("connect database(%s) failed.\n%s\n", starg.connstr, conn.m_cda.message);
                    return false;
                }
                logfile.Write("connect database(%s) ok.\n", starg.connstr);
            }

            // 处理xml文件
            int iret = _xmltodb(Dir.m_FullFileName, Dir.m_FileName);

            PActive.UptATime();

            // 处理xml文件成功，写日志，备份文件
            if (iret == 0)
            {
                logfile.WriteEx("ok(%s, total=%d, insert=%d, update=%d).\n", stxmltotable.tname, totalcount, inscount, uptcount);
                // 将xml文件移动到starg.xmlpathbak参数指定的目录中
                if (xmltobakerr(Dir.m_FullFileName, starg.xmlpath, starg.xmlpathbak) == false)
                {
                    return false;
                }
            }
            // 处理xml文件失败，分多种情况
            if ((iret == 1) || (iret == 2) || (iret == 5))
            { // 1-没有找到入库参数；2-待入库的表不存在；5-表的字段太多
                if (iret == 1)
                {
                    logfile.WriteEx("failed，没有配置入库参数.\n");
                }
                if (iret == 2)
                {
                    logfile.WriteEx("failed，待入库的表(%s)不存在.\n", stxmltotable.tname);
                }
                if (iret == 5)
                {
                    logfile.WriteEx("failed，待入库的表(%s)字段数太多.\n", stxmltotable.tname);
                }

                if (xmltobakerr(Dir.m_FullFileName, starg.xmlpath, starg.xmlpatherr) == false)
                {
                    return false;
                }
            }
            if (iret == 3)
            { // 3-打开xml文件错误，程序退出
                logfile.WriteEx("failed，打开xml文件失败\n");
                return false;
            }
            if (iret == 4)
            { // 4-数据库错误，函数返回
                logfile.WriteEx("failed，数据库错误\n");
                return false;
            }
            if (iret == 6)
            { // 6-处理xml文件之前，如果执行stxmltotable.execsql失败，函数返回，程序退出
                logfile.WriteEx("failed，执行execsql失败\n");
                return false;
            }
        }

        // 如果刚才这次扫描到了有文件，表示不空闲，可能不断的有文件生成，就不sleep了
        if (Dir.m_vFileName.size() == 0)
        {
            sleep(starg.timetvl);
        }
        PActive.UptATime();
    }
    return true;
}

// 把数据入库的参数配置文件starg.inifilename加载到vxmltotable容器中
bool loadxmltotable()
{
    vxmltotable.clear();

    CFile File;
    if (File.Open(starg.inifilename, "r") == false)
    {
        logfile.Write("File.Open(%s)失败\n", starg.inifilename);
        return false;
    }

    char strBuffer[501];
    while (true)
    {
        if (File.FFGETS(strBuffer, 500, "<endl/>") == false)
        {
            break;
        }

        memset(&stxmltotable, 0, sizeof(st_xmltotable));
        GetXMLBuffer(strBuffer, "filename", stxmltotable.filename, 100);
        GetXMLBuffer(strBuffer, "tname", stxmltotable.tname, 300);
        GetXMLBuffer(strBuffer, "uptbz", &stxmltotable.uptbz);
        GetXMLBuffer(strBuffer, "execsql", stxmltotable.execsql, 300);

        vxmltotable.push_back(stxmltotable);
    }
    logfile.Write("loadxmltotable(%s) ok.\n", starg.inifilename);

    return true;
}

// 从vxmltotable容器查找xmlfilename的入库参数，存放在stxmltotable结构体中
bool findxmltotable(char *filename)
{
    for (int i = 0; i < vxmltotable.size(); ++i)
    {
        if (MatchStr(filename, vxmltotable[i].filename) == true)
        {
            memcpy(&stxmltotable, &vxmltotable[i], sizeof(struct st_xmltotable));
            return true;
        }
    }
    return false;
}

// 调用处理xml文件子函数，返回值：0-成功，其他都是失败
int _xmltodb(char *fullfilename, char *filename)
{
    totalcount = 0;
    inscount = 0;
    uptcount = 0;

    // 从vxmltotable容器查找filename的入库参数，存放在stxmltotable结构体
    if (findxmltotable(filename) == false)
    {
        return 1;
    }

    // 释放上次处理xml文件是为字段分配的内存
    for (int i = 0; i < TABCOLS.m_allcount; ++i)
    {
        if (strcolvalue[i] != 0)
        {
            delete strcolvalue[i];
            strcolvalue[i] = 0;
        }
    }

    // 获取表全部的字段和主键信息，如果获取失败，一般是数据库连接已失效
    // 在本程序运行过程中，如果数据库出现异常，一定会在这里发现
    if (TABCOLS.allcols(&conn, stxmltotable.tname) == false)
    {
        return 4;
    }
    if (TABCOLS.pkcols(&conn, stxmltotable.tname) == false)
    {
        return 4;
    }
    // 如果TABCOLS.m_allcount为0，表示表不存在
    if (TABCOLS.m_allcount == 0)
    {
        return 2;
    }

    // 判断表的字段数不能超过MAXCOLCOUNT
    if (TABCOLS.m_allcount > MAXCOLCOUNT)
    {
        return 5;
    }
    // 为每个字段分配内存
    for (int i = 0; i < TABCOLS.m_allcount; ++i)
    {
        strcolvalue[i] = new char[TABCOLS.m_vallcols[i].collen + 1];
    }

    // 拼接生成插入和更新表数据的SQL
    crtsql();

    // prepare插入和更新的sql语句，绑定输入变量
    preparesql();

    // 处理xml文件之前，如果stxmltotable.execsql不为空，则执行
    if (execsql() == false)
    {
        return 6;
    }

    // 打开xml文件
    CFile File;
    if (File.Open(fullfilename, "r") == false)
    {
        conn.rollback();
        return 3;
    }

    char strBuffer[10241];
    while (true)
    {
        // 从xml文件中读取一行
        if (File.FFGETS(strBuffer, 10240, "<endl/>") == false)
        {
            break;
        }
        totalcount++;

        // 解析xml，存放在已绑定的输入变量中
        splitbuffer(strBuffer);

        // 执行插入和更新的sql
        if (stmtins.execute() != 0)
        {
            if (stmtins.m_cda.rc == 1)
            { // 违反唯一性约束，表示记录已存在
                // 判断入库参数的更新标志
                if (stxmltotable.uptbz == 1)
                {
                    if (stmtupt.execute() != 0)
                    {
                        // 如果update失败，记录出错的行和错误内容，函数不返回，继续处理数据
                        logfile.Write("%s", strBuffer);
                        logfile.Write("stmtupt.execute() failed.\n%s\n%s\n", stmtupt.m_sql, stmtupt.m_cda.message);

                        // 数据库连接已失效，无法继续，只能返回
                        // 3113-操作过程中服务器关闭；3114-查询过程中丢失了与mysql服务器的连接
                        if (stmtupt.m_cda.rc == 3113 || stmtupt.m_cda.rc == 3114)
                        {
                            return 4;
                        }
                    }
                    else
                    {
                        uptcount++;
                    }
                }
            }
            else
            {
                // 如果update失败，记录出错的行和错误内容，函数不返回，继续处理数据
                logfile.Write("%s", strBuffer);
                logfile.Write("stmtins.execute() failed.\n%s\n%s\n", stmtins.m_sql, stmtins.m_cda.message);

                // 数据库连接已失效，无法继续，只能返回
                // 3113-操作过程中服务器关闭；3114-查询过程中丢失了与mysql服务器的连接
                if (stmtins.m_cda.rc == 3113 || stmtins.m_cda.rc == 3114)
                {
                    return 4;
                }
            }
        }
        else
        {
            inscount++;
        }
    }

    conn.commit();

    return 0;
}

// 将xml文件移动到dstpath指定的备份/错误目录中
bool xmltobakerr(char *fullfilename, char *srcpath, char *dstpath)
{
    char dstfilename[301];
    STRCPY(dstfilename, sizeof(dstfilename), fullfilename);
    UpdateStr(dstfilename, srcpath, dstpath, false);
    if (RENAME(fullfilename, dstfilename) == false)
    {
        logfile.Write("RENAME(%s,%s) failed.\n", fullfilename, dstfilename);
        return false;
    }

    return true;
}

void crtsql()
{
    memset(strinsertsql, 0, sizeof(strinsertsql));
    memset(strupdatesql, 0, sizeof(strupdatesql));

    // 生成插入表的SQL语句 insert into 表名(%s) values(%s)
    char strinserttmp1[3001]; // insert语句的字段列表
    char strinserttmp2[3001]; // insert语句values后的内容
    memset(strinserttmp1, 0, sizeof(strinserttmp1));
    memset(strinserttmp2, 0, sizeof(strinserttmp2));

    int colseq = 1; // value部分字段的序号
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // upttime字段不需要处理
        if (strcmp(TABCOLS.m_vallcols[i].colname, "upttime") == 0)
        {
            continue;
        }

        // 拼接strinserttmp1
        strcat(strinserttmp1, TABCOLS.m_vallcols[i].colname);
        strcat(strinserttmp1, ",");

        // 拼接strinserttmp2，需要区分keyid字段、date和非date字段
        char strtemp[101];
        // keyid字段需要特殊处理
        if (strcmp(TABCOLS.m_vallcols[i].colname, "keyid") == 0)
        {
            SNPRINTF(strtemp, 100, sizeof(strtemp), "SEQ_%s.nextval", stxmltotable.tname + 2);
        }
        else
        {
            if (strcmp(TABCOLS.m_vallcols[i].datatype, "date") != 0)
            {
                SNPRINTF(strtemp, 100, sizeof(strtemp), ":%d", colseq);
            }
            else
            {
                SNPRINTF(strtemp, 100, sizeof(strtemp), "to_date(:%d, 'yyyymmddhh24miss')", colseq);
            }
            colseq++;
        }
        strcat(strinserttmp2, strtemp);
        strcat(strinserttmp2, ",");
    }
    strinserttmp1[strlen(strinserttmp1) - 1] = 0; // 删除最后一个多余的逗号
    strinserttmp2[strlen(strinserttmp2) - 1] = 0; // 删除最后一个多余的逗号

    SNPRINTF(strinsertsql, 10240, sizeof(strinsertsql), "insert into %s(%s) values(%s)", stxmltotable.tname, strinserttmp1, strinserttmp2);
    logfile.Write("strinsertsql=%s\n", strinsertsql);

    if (stxmltotable.uptbz != 1)
    {
        return;
    }
    // 生成修改表的SQL语句
    // 更新TABCOLS.m_vallcols中pkseq字段，在拼接update语句的时候要用到
    for (int i = 0; i < TABCOLS.m_vpkcols.size(); ++i)
    {
        for (int j = 0; j < TABCOLS.m_vallcols.size(); ++j)
        {
            if (strcmp(TABCOLS.m_vpkcols[i].colname, TABCOLS.m_vallcols[j].colname) == 0)
            {
                // 更新m_vallcols容器中的pkseq
                TABCOLS.m_vallcols[j].pkseq = TABCOLS.m_vpkcols[i].pkseq;
                break;
            }
        }
    }

    // 拼接update语句开始的部分
    sprintf(strupdatesql, "update %s set ", stxmltotable.tname);

    // 拼接update语句set后的部分
    colseq = 1;
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // keyid字段不需要处理
        if (strcmp(TABCOLS.m_vallcols[i].colname, "keyid") == 0)
        {
            continue;
        }

        // 主键字段也不需要拼接在set的后面
        if (TABCOLS.m_vallcols[i].pkseq != 0)
        {
            continue;
        }

        // upttime字段直接等于now()，考虑兼容性
        if (strcmp(TABCOLS.m_vallcols[i].colname, "upttime") == 0)
        {
            strcat(strupdatesql, "upttime=sysdate,");
            continue;
        }

        // 其他字段需要区分date字段和非date字段
        char strtemp[101];
        if (strcmp(TABCOLS.m_vallcols[i].datatype, "date") != 0)
        {
            SNPRINTF(strtemp, 100, sizeof(strtemp), "%s=:%d", TABCOLS.m_vallcols[i].colname, colseq);
        }
        else
        {
            SNPRINTF(strtemp, 100, sizeof(strtemp), "%s=to_date(:%d, 'yyyymmddhh24miss')", TABCOLS.m_vallcols[i].colname, colseq);
        }
        strcat(strupdatesql, strtemp);
        strcat(strupdatesql, ",");
        colseq++;
    }
    strupdatesql[strlen(strupdatesql) - 1] = 0; // 删除最后一个多余的逗号

    // 拼接update语句where后面的部分
    strcat(strupdatesql, " where 1=1 "); // 用1=1是为了后面的拼接方便，这是常用的处理方法

    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // 如果不是主键字段，跳过
        if (TABCOLS.m_vallcols[i].pkseq == 0)
        {
            continue;
        }

        // 把主键字段拼接到update语句中，需要区分date字段和非date字段
        char strtemp[101];
        if (strcmp(TABCOLS.m_vallcols[i].datatype, "date") != 0)
        {
            SNPRINTF(strtemp, 100, sizeof(strtemp), " and %s=:%d", TABCOLS.m_vallcols[i].colname, colseq);
        }
        else
        {
            SNPRINTF(strtemp, 100, sizeof(strtemp), " and %s=to_date(:%d, 'yyyymmddhh24miss')", TABCOLS.m_vallcols[i].colname, colseq);
        }
        strcat(strupdatesql, strtemp);
        colseq++;
    }

    // logfile.Write("strupdatesql=%s\n", strupdatesql);
}

void preparesql()
{
    // 绑定插入sql语句的输入变量
    stmtins.connect(&conn);
    stmtins.prepare(strinsertsql);
    // logfile.Write("strinsertsql=%s\n", strinsertsql);

    int colseq = 1; // values部分字段的序号
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // upttime和keyid两个字段不需要处理
        if ((strcmp(TABCOLS.m_vallcols[i].colname, "upttime") == 0) || (strcmp(TABCOLS.m_vallcols[i].colname, "keyid") == 0))
        {
            continue;
        }

        // strcolvalue数组的使用不是连续的，是和TABCOLS.m_vallcols的下标一一对应的
        stmtins.bindin(colseq, strcolvalue[i], TABCOLS.m_vallcols[i].collen);
        // logfile.Write("stmtins.bindin(%d, %s, %d)\n", colseq, TABCOLS.m_vallcols[i].colname, TABCOLS.m_vallcols[i].collen);
        colseq++;
    }

    // 绑定更新sql语句的输入变量
    // 如果入库参数中指定了表数据不需要更新，就不处理update语句
    if (stxmltotable.uptbz != 1)
    {
        return;
    }

    stmtupt.connect(&conn);
    stmtupt.prepare(strupdatesql);
    // logfile.Write("strupdatesql=%s\n", strupdatesql);

    colseq = 1; // set和where部分字段的序号
    // 绑定set部分的输入参数
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // upttime和keyid两个字段不需要处理
        if ((strcmp(TABCOLS.m_vallcols[i].colname, "upttime") == 0) || (strcmp(TABCOLS.m_vallcols[i].colname, "keyid") == 0))
        {
            continue;
        }

        // 如果是主键字段，不需要拼接在set后面
        if (TABCOLS.m_vallcols[i].pkseq != 0)
        {
            continue;
        }

        // strcolvalue数组的使用不是连续的，是和TABCOLS.m_vallcols的下标一一对应的
        stmtupt.bindin(colseq, strcolvalue[i], TABCOLS.m_vallcols[i].collen);
        // logfile.Write("stmtupt.bindin(%d, %s, %d)\n", colseq, TABCOLS.m_vallcols[i].colname, TABCOLS.m_vallcols[i].collen);
        colseq++;
    }
    // 绑定where部分的输入参数
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // 如果不是主键字段则跳过，只有主键字段需要拼接在where后面
        if (TABCOLS.m_vallcols[i].pkseq == 0)
        {
            continue;
        }

        // strcolvalue数组的使用不是连续的，是和TABCOLS.m_vallcols的下标一一对应的
        stmtupt.bindin(colseq, strcolvalue[i], TABCOLS.m_vallcols[i].collen);
        colseq++;
        // logfile.Write("stmtupt.bindin(%d, %s, %d)\n", colseq, TABCOLS.m_vallcols[i].colname, TABCOLS.m_vallcols[i].collen);
    }
}

bool execsql()
{
    if (strlen(stxmltotable.execsql) == 0)
    {
        return true;
    }

    sqlstatement stmt(&conn);
    stmt.prepare(stxmltotable.execsql);
    if (stmt.execute() != 0)
    {
        logfile.Write("stmt.execute() failed.\n%s\n%s\n", stmt.m_sql, stmt.m_cda.message);
        return false;
    }

    return true;
}

void splitbuffer(char *strBuffer)
{
    // memset(strcolvalue, 0, sizeof(strcolvalue));
    for (int i = 0; i < TABCOLS.m_allcount; ++i)
    {
        memset(strcolvalue[i], 0, TABCOLS.m_vallcols[i].collen + 1);
    }

    char strtemp[31];
    for (int i = 0; i < TABCOLS.m_vallcols.size(); ++i)
    {
        // 如果是日期时间字段，提取数值就可以
        if (strcmp(TABCOLS.m_vallcols[i].datatype, "date") == 0)
        {
            GetXMLBuffer(strBuffer, TABCOLS.m_vallcols[i].colname, strtemp, TABCOLS.m_vallcols[i].collen);
            PickNumber(strtemp, strcolvalue[i], false, false);
            continue;
        }
        // 如果是数值字段，只提取数字、+-符号和圆点
        if (strcmp(TABCOLS.m_vallcols[i].datatype, "number") == 0)
        {
            GetXMLBuffer(strBuffer, TABCOLS.m_vallcols[i].colname, strtemp, TABCOLS.m_vallcols[i].collen);
            PickNumber(strtemp, strcolvalue[i], true, true);
            continue;
        }

        // 如果是字符字段，直接提取
        GetXMLBuffer(strBuffer, TABCOLS.m_vallcols[i].colname, strcolvalue[i], TABCOLS.m_vallcols[i].collen);
    }
}
